---
title: 命令模式
description: Command Pattern - 将一个请求封装为一个对象，从而使你可用不同的请求对客户进行参数化
sidebar_position: 2
---

## 定义
**命令模式（command pattern）：命令模式将请求（命令）封装为一个对象，这样可以使用不同的请求参数化其它对象（将不同请求依赖注入到其它对象），并且能够支持请求（命令）的排队执行、记录日志、撤销等（附加控制）功能**

命令模式的核心是将指令信息封装成一个对象，并将此对象作为参数发送给接收方去执行，达到使命令的请求与执行方解耦，双方只通过传递各种命令对象来完成任务。

在实际的开发中，如果用到的编程语言并不支持用函数作为参数来传递，那么就可以借助命令模式将函数封装为对象来使用。

:::tip
C 语言支持函数指针，我们可以把函数当作变量传递来传递去。但是，在大部分编程语言中，函数没法作为参数传递给其它函数，也没法赋值给变量。借助命令模式，我们可以将函数封装成对象。具体来说就是，设计一个包含这个函数的类，实例化一个对象传来传去，这样就可以实现把函数像对象一样使用。
:::

## 模式原理
* **Command**：抽象的命令类，定义命令的接口。
* **ConcreteCommand**：具体命令角色，通常会持有接收者的引用，并调用接收者的功能来完成命令要执行的相应的功能。
* **Receiver（接收者）**：真正执行命令的对象。
* **Invoker（调用者）**：持有命令对象的引用，调用命令对象来执行请求。

```mermaid
classDiagram
    class Command {
        <<interface>>
        +execute()
    }
    
    class ConcreteCommand {
        -receiver: Receiver
        -state: Object
        +execute()
        +setReceiver(receiver)
    }
    
    class Receiver {
        +action()
    }
    
    class Invoker {
        -command: Command
        +setCommand(command)
        +executeCommand()
    }
    
    class Client {
        +main()
    }
    
    Command <|.. ConcreteCommand
    ConcreteCommand --> Receiver : uses
    Invoker --> Command : calls
    Client --> Invoker : creates
    Client --> ConcreteCommand : creates
    Client --> Receiver : creates
```

### 设计要点
1. **解耦请求与执行**：通过命令对象将请求发送者与请求执行者解耦
2. **支持队列操作**：可以轻松实现命令的排队执行
3. **支持撤销操作**：可以保存命令历史，实现撤销功能
4. **支持日志记录**：可以记录命令的执行过程

## 应用案例
模拟酒店后厨的出餐流程，来对命令模式进行演示，命令模式的角色与案例中的角色对应关系如下：
* **服务员**：即调用者角色，由它来发起命令。
* **厨师**：接收者，真正执行命令的对象
* **订单**：命令中包含订单。

### 核心类实现

#### 1. 抽象命令接口（Command）
```java
/**
 * 抽象命令接口
 */
public interface Command {
    // 统一的执行方法
    void execute();
}
```

#### 2. 具体命令类（OrderCommand）
```java
/**
 * 具体命令
 */
public class OrderCommand implements Command {
    // 持有接收者对象引用
    private Chef receiver;
    private Order order;

    public OrderCommand(Chef receiver, Order order) {
        this.receiver = receiver;
        this.order = order;
    }

    @Override
    public void execute() {
        System.out.println(order.getDiningTable() + "桌的订单：");
        Set<String> keySet = order.getDooMenu().keySet();
        for (String key : keySet) {
            Integer num = order.getDooMenu().get(key);
            receiver.makeFood(num, key);
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(order.getDiningTable() + "桌的菜品已经上齐了");
    }
}
```

#### 3. 接收者类（Chef）
```java
/**
 * 厨师类 -> Receiver 接受者角色
 */
public class Chef {
    public void makeFood(int num, String foodName) {
        System.out.println(num + "份" + foodName);
    }
}
```

#### 4. 调用者类（Waiter）
```java
import java.util.ArrayList;
import java.util.List;

/**
 * 服务员 -> Invoker 调用者
 */
public class Waiter {
    // 可以持有多个命令对象
    private List<Command> commands;

    public Waiter() {
        commands = new ArrayList<>();
    }

    public Waiter(List<Command> commands) {
        this.commands = commands;
    }

    public void addCommands(Command command) {
        this.commands.add(command);
    }

    // 发出指令
    public void orderUp() {
        System.out.println("叮咚！服务员：有新的订单，请师傅开始制作...");
        for (Command command : commands) {
            command.execute();
        }
    }
}
```

#### 5. 订单类（Order）
```java
import java.util.HashMap;
import java.util.Map;

/**
 * 订单类
 */
public class Order {
    // 餐桌号码
    private int diningTable;
    // 存储菜名和份数
    private Map<String, Integer> dooMenu = new HashMap<>();

    public int getDiningTable() {
        return diningTable;
    }

    public void setDiningTable(int diningTable) {
        this.diningTable = diningTable;
    }

    public Map<String, Integer> getDooMenu() {
        return dooMenu;
    }

    public void setDooMenu(Map<String, Integer> dooMenu) {
        this.dooMenu = dooMenu;
    }
}
```

#### 6. 客户端测试（Client）
```java
public class Client {
    public static void main(String[] args) {
        // 创建订单1
        Order order1 = new Order();
        order1.setDiningTable(10);
        order1.getDooMenu().put("蛋炒饭", 1);
        order1.getDooMenu().put("海参炒面", 1);

        // 创建订单2
        Order order2 = new Order();
        order2.setDiningTable(11);
        order2.getDooMenu().put("回锅肉", 1);
        order2.getDooMenu().put("木须肉", 1);

        // 创建接收者
        Chef chef = new Chef();

        // 将订单和接收者封装成命令对象
        Command cmd1 = new OrderCommand(chef, order1);
        Command cmd2 = new OrderCommand(chef, order2);

        // 创建调用者
        Waiter waiter = new Waiter();
        waiter.addCommands(cmd1);
        waiter.addCommands(cmd2);

        // 将订单发送给厨师，上菜
        waiter.orderUp();
    }
}
```

### 运行结果示例
```
叮咚！服务员：有新的订单，请师傅开始制作...
10桌的订单：
1份蛋炒饭
1份海参炒面
10桌的菜品已经上齐了
11桌的订单：
1份回锅肉
1份木须肉
11桌的菜品已经上齐了
```

### 案例分析

#### 1. **角色职责**
- **Waiter（调用者）**：负责接收订单并调用命令执行
- **OrderCommand（具体命令）**：封装订单信息，调用厨师执行
- **Chef（接收者）**：实际执行烹饪操作
- **Order（订单）**：包含订单的详细信息

#### 2. **设计优势**
- **解耦**：服务员不需要直接与厨师交互，通过命令对象传递
- **扩展性**：可以轻松添加新的命令类型（如取消订单、修改订单等）
- **队列支持**：可以批量处理多个订单
- **日志记录**：可以记录每个订单的处理过程

#### 3. **实际应用价值**
- **餐厅管理系统**：订单处理、菜品制作流程
- **任务调度系统**：任务队列管理、执行状态跟踪
- **游戏开发**：玩家操作记录、撤销功能
- **GUI开发**：按钮点击事件、菜单操作

## 总结

### 优点
* **降低系统的耦合度**：命令模式能将调用操作的对象与实现该操作的对象解耦。
* **增加或删除命令非常方便**：采用命令模式增加与删除命令不会影响其他类，它满足"开闭原则"，对扩展比较灵活。
* **可以实现宏命令**：命令模式可以与组合模式结合，将多个命令装配成一个组合命令，即宏命令。

### 缺点
* **使用命令模式可能会导致某些系统有过多的具体命令类**：每个命令都需要一个具体的命令类，可能导致类的数量过多。
* **系统结构更加复杂**：引入了额外的命令类，增加了系统的复杂度。

### 使用场景
* **系统需要将请求调用者和请求接收者解耦，使得调用者和接收者不直接交互**：如GUI事件处理、网络请求处理等。
* **系统需要在不同的时间指定请求、将请求排队和执行请求**：如任务调度系统、消息队列系统等。
* **系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作**：如文本编辑器、图形编辑器等。

### 最佳实践
1. **合理设计命令接口**：确保命令接口简洁明了，便于实现和维护
2. **避免命令对象过重**：不要在命令对象中放置过多的业务逻辑
3. **考虑命令的撤销**：在设计命令时，考虑如何实现撤销功能
4. **使用命令队列**：对于需要批量处理的场景，使用命令队列提高效率
5. **记录命令日志**：记录命令的执行过程，便于问题排查和审计

### 与其他模式的关系
* **与备忘录模式结合**：可以实现命令的撤销和重做功能
* **与策略模式结合**：可以为不同的命令类型提供不同的执行策略
* **与观察者模式结合**：可以通知其他对象命令的执行状态
* **与组合模式结合**：可以实现宏命令功能

## 更新历史
- 2024-01-01: 完善命令模式文档，补充完整的代码实现示例和应用案例

